/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace DaveSexton.DocProject
{
	[Serializable]
	public sealed class SourceCollection : Collection<Source>
	{
		#region Public Properties
		public ICollection<SystemPath> Inputs
		{
			get
			{
				List<SystemPath> inputs = new List<SystemPath>();

				foreach (Source source in this)
					inputs.Add(source.Input);

				return inputs.AsReadOnly();
			}
		}
		#endregion

		#region Private / Protected
		private readonly IEnumerable<string> validSourceExtensions;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="SourceCollection" /> class.
		/// </summary>
		public SourceCollection(IEnumerable<string> validSourceExtensions)
		{
			if (validSourceExtensions == null)
				throw new ArgumentNullException("validSourceExtensions");

			this.validSourceExtensions = validSourceExtensions;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SourceCollection" /> class.
		/// </summary>
		public SourceCollection(IEnumerable<string> validSourceExtensions, IEnumerable<Source> sources)
		{
			if (validSourceExtensions == null)
				throw new ArgumentNullException("validSourceExtensions");

			this.validSourceExtensions = validSourceExtensions;

			foreach (Source source in sources)
			{
				if (source == Source.Empty)
					throw new ArgumentException(Resources.Errors.SourceEmpty, "sources");
				
				base.Items.Add(source);
			}
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SourceCollection" /> class.
		/// </summary>
		private SourceCollection(IEnumerable<string> validSourceExtensions, IList<Source> sources)
			: base(sources)
		{
			this.validSourceExtensions = validSourceExtensions;
		}
		#endregion

		#region Methods
		private static SystemPath ValidateXmlDocumentationPath(string basePath, string path)
		{
			if (string.IsNullOrEmpty(path))
				return SystemPath.Empty;
			else if (PathFormat.IsValid(path, false))
			{
				SystemPath source = new SystemPath(basePath, path, true);

				if (source.IsDirectory || source.HasFileExtension(".xml"))
					return source;
			}
			
			throw new FormatException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
				Resources.Errors.InvalidSourceXmlDocumentationPath, path));
		}

		private static SystemPath ValidateSourcePath(string basePath, string path, IEnumerable<string> validSourceExtensions)
		{
			if (string.IsNullOrEmpty(path))
				return SystemPath.Empty;
			else if (PathFormat.IsValid(path, false))
			{
				SystemPath source = new SystemPath(basePath, path, true);

				if (source.IsDirectory || source.HasFileExtension(validSourceExtensions))
					return source;
			}

			throw new FormatException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
				Resources.Errors.InvalidSourcePath, path));
		}

		public SourceCollection AsReadOnly()
		{
			return new SourceCollection(validSourceExtensions, new List<Source>(this).AsReadOnly());
		}

		public void AddRange(IEnumerable<Source> sources)
		{
			if (sources == null)
				throw new ArgumentNullException("sources");

			foreach (Source source in sources)
				Add(source);
		}

		public void AddFromList(string basePath, string sources)
		{
			if (string.IsNullOrEmpty(sources))
				return;

			foreach (string pair in sources.Split(new string[] { "||" }, StringSplitOptions.RemoveEmptyEntries))
			{
				string[] paths = pair.Split('|');

				SystemPath sourcePath = ValidateSourcePath(basePath, paths[0], validSourceExtensions);

				if (paths.Length == 1)
				{
					if (sourcePath != SystemPath.Empty)
						Add(new Source(sourcePath, SystemPath.Empty));
				}
				else
				{
					SystemPath xmlDocumentationPath = ValidateXmlDocumentationPath(basePath, paths[1]);

					if (sourcePath != SystemPath.Empty || xmlDocumentationPath != SystemPath.Empty)
						Add(new Source(sourcePath, xmlDocumentationPath));
				}
			}
		}

		public string ToListString(string makeRelativeToBasePath)
		{
			StringBuilder sources = new StringBuilder();

			foreach (Source source in this)
			{
				sources.Append(source.Input.CompleteRelativeTo(makeRelativeToBasePath));

				if (source.XmlDocumentation != SystemPath.Empty)
				{
					sources.Append('|');
					sources.Append(source.XmlDocumentation.CompleteRelativeTo(makeRelativeToBasePath));
				}

				sources.Append("||");
			}

			if (Count > 0)
				// remove trailing separator
				sources.Remove(sources.Length - 2, 2);

			return sources.ToString();
		}

		protected override void InsertItem(int index, Source item)
		{
			if (item == Source.Empty)
				throw new ArgumentException(Resources.Errors.SourceEmpty, "item");

			base.InsertItem(index, item);

			OnItemAdded(new SourceEventArgs(item));
		}

		protected override void SetItem(int index, Source item)
		{
			if (item == Source.Empty)
				throw new ArgumentException(Resources.Errors.SourceEmpty, "item");

			Source existingItem = this[index];
			
			if (!item.Equals(existingItem))
			{
				base.SetItem(index, item);

				OnItemRemoved(new SourceEventArgs(existingItem));
				OnItemAdded(new SourceEventArgs(item));
			}
		}

		protected override void ClearItems()
		{
			// clear items one-by-one so that the ItemRemoved event is raised for each
			while (Count > 0)
				RemoveAt(0);
		}

		protected override void RemoveItem(int index)
		{
			Source item = this[index];

			base.RemoveItem(index);

			OnItemRemoved(new SourceEventArgs(item));
		}
		#endregion

		#region Events
		private readonly object ItemAddedEventLock = new object();
		private EventHandler<SourceEventArgs> ItemAddedEvent;
		private readonly object ItemRemovedEventLock = new object();
		private EventHandler<SourceEventArgs> ItemRemovedEvent;

		/// <summary>
		/// Event raised when a <see cref="Source"/> is removed.
		/// </summary>
		public event EventHandler<SourceEventArgs> ItemRemoved
		{
			add
			{
				lock (ItemRemovedEventLock)
				{
					ItemRemovedEvent += value;
				}
			}
			remove
			{
				lock (ItemRemovedEventLock)
				{
					ItemRemovedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ItemRemoved" /> event.
		/// </summary>
		/// <param name="e"><see cref="SourceEventArgs" /> object that provides the arguments for the event.</param>
		private void OnItemRemoved(SourceEventArgs e)
		{
			EventHandler<SourceEventArgs> handler = null;

			lock (ItemRemovedEventLock)
			{
				handler = ItemRemovedEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when a <see cref="Source"/> is added.
		/// </summary>
		public event EventHandler<SourceEventArgs> ItemAdded
		{
			add
			{
				lock (ItemAddedEventLock)
				{
					ItemAddedEvent += value;
				}
			}
			remove
			{
				lock (ItemAddedEventLock)
				{
					ItemAddedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ItemAdded" /> event.
		/// </summary>
		/// <param name="e"><see cref="SourceEventArgs" /> object that provides the arguments for the event.</param>
		private void OnItemAdded(SourceEventArgs e)
		{
			EventHandler<SourceEventArgs> handler = null;

			lock (ItemAddedEventLock)
			{
				handler = ItemAddedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion
	}
}
